package main

import (
	"GpsCal/gps" //引用Distance函数计算两个点之间的距离
	"fmt"
	"runtime"
	"sort"
	"strconv"
	"time"

	"github.com/tealeg/xlsx" //第三方库，读取写入Excel表格数据
)

/*******************************多协程版本*****************************/

//存入jobs通道的记录，代表一个计算任务。按jobNo顺序存入通道
type job struct {
	jobNo int //工作号，按顺序存入通道
	name  string
	lon   float64
	lat   float64
}

//计算任务结果，存入在results通道，因为是多协程并发计算，存入通道的结果是乱序的。
type result struct {
	jobNo int     //对应jobs通道里的jobNo
	name  string  //最近点的名称
	d     float64 //最近点的距离

}

//Results 利用sort包排序
type Results []result

func (r Results) Len() int {
	return len(r)
}
func (r Results) Less(i, j int) bool {
	return r[i].jobNo < r[j].jobNo
}
func (r Results) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

//存放计算结果的slice
var sliceResults Results

//工作协程，根据CPU数量开启。每个协程从jobs通道取出任务，再根据任务里的lon,lat计算sheet2中最近点的距离。然后把结果存入results通道
func worker(workerNo int, jobs <-chan job, results chan<- result, sheet2 *xlsx.Sheet) {
	for {
		//从jobs通道中取job
		tmpJob, ok := <-jobs
		if !ok {
			break
		}
		lon1 := tmpJob.lon
		lat1 := tmpJob.lat
		//将job中的jobNo放入返回结果
		tmpRes := result{
			jobNo: tmpJob.jobNo,
			name:  tmpJob.name,
			d:     0,
		}
		//计算job中的经纬度与表2中所有点最近的一个点
		d := 1000000000.000
		name := ""
		for j := 1; j < sheet2.MaxRow; j++ {
			row := sheet2.Rows[j]

			lon2, _ := strconv.ParseFloat(row.Cells[1].Value, 64)
			lat2, _ := strconv.ParseFloat(row.Cells[2].Value, 64)
			tmp := gps.Distance(lon1, lat1, lon2, lat2)
			if d > tmp {
				d = tmp
				name = row.Cells[0].Value
			}

		}
		//将计算结果放入返回结果
		tmpRes.d = d
		tmpRes.name = name
		//计算结果存入通道results
		results <- tmpRes

		fmt.Printf("Worker No.%2d 正在计算第%5d 行\n", workerNo, tmpRes.jobNo)
	}

}

//校验表格数据是否规范
func checkData(xlFile *xlsx.File) error {
	sheet1 := xlFile.Sheets[0]
	sheet2 := xlFile.Sheets[1]
	//Sheet1检查
	for i := 1; i < sheet1.MaxRow; i++ {
		row := sheet1.Rows[i]
		lon, err := strconv.ParseFloat(row.Cells[1].Value, 64)
		if err != nil {
			return err
		}
		lat, err := strconv.ParseFloat(row.Cells[2].Value, 64)
		if err != nil {
			return err
		}
		if lon < 0 || lon > 180 {
			return err
		}
		if lat < 0 || lon > 90 {
			return err
		}
	}
	//Sheet2检查
	for j := 1; j < sheet2.MaxRow; j++ {

		row := sheet1.Rows[j]
		lon, err := strconv.ParseFloat(row.Cells[1].Value, 64)
		if err != nil {
			return err
		}
		lat, err := strconv.ParseFloat(row.Cells[2].Value, 64)
		if err != nil {
			return err
		}
		if lon < 0 || lon > 180 {
			return err
		}
		if lat < 0 || lon > 90 {
			return err
		}
	}
	return nil
}

func main() {

	start := time.Now()

	//读取EXCEL文件
	excelFileName := "test.xlsx"
	xlFile, err := xlsx.OpenFile(excelFileName)
	if err != nil {
		fmt.Printf("open failed: %s\n", err)
		return
	}
	//校验表格中数据是否规范
	err = checkData(xlFile)
	if err != nil {
		fmt.Printf("表格中数据有错误！%s\n", err)
		return
	}

	sheet1 := xlFile.Sheets[0]
	sheet2 := xlFile.Sheets[1]
	maxRow := sheet1.MaxRow
	fmt.Println("共有：", maxRow-1, "行")
	//表1增加两列用于存放最近点名称，距离
	row := sheet1.Rows[0]
	cell := row.AddCell()
	cell.Value = "最近点名称"
	cell = row.AddCell()
	cell.Value = "最近点距离"
	cell = row.AddCell()
	cell.SetValue("JobNo")

	//创建两个通道
	jobs := make(chan job, maxRow)
	results := make(chan result, maxRow)

	//将工作任务顺序存入jobs通道
	for i := 1; i < maxRow; i++ {

		row = sheet1.Rows[i]
		name := row.Cells[0].Value
		lon1, _ := strconv.ParseFloat(row.Cells[1].Value, 64)
		lat1, _ := strconv.ParseFloat(row.Cells[2].Value, 64)
		tmp := job{
			jobNo: i,
			name:  name,
			lon:   lon1,
			lat:   lat1,
		}
		jobs <- tmp
	}
	close(jobs)
	//工作goroutine从jobs通道中取出任务，并行计算后把结果存入results通道
	nCPU := runtime.NumCPU()
	for i := 0; i < nCPU; i++ {
		go worker(i+1, jobs, results, sheet2)
	}

	//从results通道中取出计算结果到数组中，并根据jobNo对数组进行排序，以得到按顺序排列的结果

	sliceResults = make([]result, maxRow-1)
	for j := 1; j < maxRow; j++ {
		tmpRes, ok := <-results
		if !ok {
			break
		}
		sliceResults[j-1] = tmpRes
	}
	//数组中的结果是乱序的。需要排序。
	sortByJobNo(sliceResults)

	for j := 1; j < maxRow; j++ {

		row = sheet1.Rows[j]

		cell = row.AddCell()
		cell.Value = sliceResults[j-1].name
		cell = row.AddCell()
		cell.Value = strconv.FormatFloat(sliceResults[j-1].d, 'f', -1, 32)

		cell = row.AddCell()
		cell.Value = strconv.FormatInt(int64(sliceResults[j-1].jobNo), 10)
	}

	xlFile.Save(excelFileName)

	fmt.Println("\n并发CPU数量：", nCPU)
	end := time.Now()
	fmt.Println("耗时：", end.Sub(start).Seconds(), "秒")
	fmt.Println("按回车键退出")
	s := ""
	fmt.Scanln(&s)

}

//对结果数组进行排序
func sortByJobNo(a Results) {
	leng := len(a)
	fmt.Println("排序：", leng, "行")
	//排序操作
	sort.Sort(a)
	fmt.Println("排序完成")
}
